﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Threading;

using agsXMPP;
using agsXMPP.protocol.client;
using agsXMPP.protocol.iq.disco;

using VSTalk.Engine.Core.Context;
using VSTalk.Engine.Core.XmppConnection;
using VSTalk.Model;
using VSTalk.Tools;

namespace VSTalk.Engine.Core.Disco.Caps
{
    public class CapsDiscoveryNetAccess : ICapsDiscoveryNetAccess, IListener
    {
        private readonly HashSet<CapsInfo> _pendingRequests = new HashSet<CapsInfo>();
        private readonly IDictionary<Client, XmppClientConnection> _clientConnections = new Dictionary<Client, XmppClientConnection>();
        private readonly IDictionary<string, CapsInfo> _requestIdToCapsInfo = new Dictionary<string, CapsInfo>();
        private ISubscription _clientSub;
        private IActionWrapper<object, IQ> _onIq;

        public void StartListen()
        {
            _clientSub = ModelContext.Clients.HandleRemoveChanges(removed: ClientRemoved);
            var dispatcher = Dispatcher.CurrentDispatcher;
            _onIq = dispatcher.Wrap<Object, IQ>(OnIq);
        }

        public void StopListen()
        {
            _clientSub.Unsubscribe();
            foreach (var xmppClientConnection in _clientConnections)
            {
                xmppClientConnection.Value.OnIq -= _onIq.Exec;
            }
        }
        private static IModelContext ModelContext
        {
            get { return AppServices.Get<IModelContext>(); }
        }

        private static IConnectionManager ConnectionManager
        {
            get { return AppServices.Get<IConnectionManager>(); }
        }

        private void ClientRemoved(Client client)
        {
            if (!_clientConnections.ContainsKey(client))
            {
                return;
            }
            var connection = _clientConnections[client];
            connection.OnIq -= _onIq.Exec;
            _clientConnections.Remove(client);
        }

        private XmppClientConnection GetConnection(Client client)
        {
            if (_clientConnections.ContainsKey(client))
            {
                return _clientConnections[client];
            }
            var connection = ConnectionManager.GetOrCreateConnection(client).Connection;
            _clientConnections.Add(client, connection);
            connection.OnIq += _onIq.Exec;
            return connection;
        }

        public void RequestFeatures(CapsSource source, CapsInfo info)
        {
            if (_pendingRequests.Contains(info))
            {
                return;
            }
            _pendingRequests.Add(info);
            var connection = GetConnection(source.Client);
            var requestId = Guid.NewGuid().ToString("N");
            _requestIdToCapsInfo.Add(requestId, info);

            var iq = new DiscoInfoIq(IqType.get);
            iq.Id = requestId;
            iq.From = connection.MyJID;
            iq.To = new Jid(source.EntityJid);
            if (info.Hash != null && info.Node != null && info.Version != null)
            {
                iq.Query.Node = string.Format("{0}#{1}", info.Node, info.Version);
            }
            connection.Send(iq);
        }

        private void OnIq(object sender, IQ iq)
        {
            if (!_requestIdToCapsInfo.ContainsKey(iq.Id) || iq.Type != IqType.result)
            {
                return;
            }
            var capsInfo = _requestIdToCapsInfo[iq.Id]; 
            _pendingRequests.Remove(capsInfo);
            _requestIdToCapsInfo.Remove(iq.Id);
            if (iq.Error != null || iq.Query == null)
            {
                return;
            }
            var result = iq.Query.ChildNodes.OfType<DiscoFeature>().Select(node => node.Var).ToList();
            OnFeaturesReceived(capsInfo, result);
        }

        public event Action<CapsInfo, IList<string>> FeaturesReceived;
        protected virtual void OnFeaturesReceived(CapsInfo arg1, IList<string> arg2)
        {
            var handler = FeaturesReceived;
            if (handler != null) handler(arg1, arg2);
        }
    }
}